﻿namespace Chapter6

  (*
  Audio ( * )

  In this chapter we show how to add audio to our game.
  It is incredibly simple, and yet audio can give a great depth to a scene.

  The entire game is exactly the same as in last section, with less than 10 lines more.
  *)
  module WithAudio =
    open System
    open System.Collections.Generic
    open Microsoft.Xna.Framework
    open Microsoft.Xna.Framework.Graphics
    open Microsoft.Xna.Framework.Input
    open Microsoft.Xna.Framework.Audio
    open Microsoft.Xna.Framework.Media
    open Chapter5

    type Chapter2.Math.Vector2< [<Measure>] 'u >
      with
        member this.ToXNAVector = Vector2(this.X |> float32, this.Y |> float32)

    type Game3D() as this = 
      inherit Game()
      let graphics = new GraphicsDeviceManager(this)
      do graphics.GraphicsProfile           <- GraphicsProfile.HiDef
      do graphics.IsFullScreen              <- true
      do graphics.PreferredBackBufferWidth  <- 1280
      do graphics.PreferredBackBufferHeight <- 800
      let mutable sprite_batch   : SpriteBatch = null
      let mutable basic_effect   : BasicEffect = null

      let mutable police_ship    : Model = null
      let mutable pirate_ship    : Model = null
      let mutable cargo_ship     : Model = null
      let mutable police_station : Model = null

      let mutable stars  : Texture2D = null
      let mutable nebula : Texture2D = null

      let mutable laser  : Texture2D = null

      (*
      When a ship is destroyed we will play an explosion sound. 
      To avoid playing the explosion sound every frame a ship is destroyed, we keep track if the sound has been played already:
      *)
      let mutable explosion_played = false

      let mutable zoom = 1.0f
      let mutable center = Vector3.Zero

      let mutable view = Matrix.CreateLookAt(Vector3.Forward * 30.0f, Vector3.Zero, Vector3.Up)
      let projection = Matrix.CreatePerspectiveFieldOfView(1.5f, 1.6f, 0.1f, 1000.0f)
      let police_chase = PoliceChase.s0()

      do this.Content.RootDirectory <- "Content"

      override this.Initialize() = 
        sprite_batch <- new SpriteBatch(this.GraphicsDevice)
        basic_effect <- new BasicEffect(this.GraphicsDevice)

        do basic_effect.TextureEnabled <- true
        do basic_effect.VertexColorEnabled <- true

        base.Initialize()
  
      override this.LoadContent() = 
        do police_ship    <- this.Content.Load(@"3D\A1")
        do pirate_ship    <- this.Content.Load(@"3D\A2")
        do cargo_ship     <- this.Content.Load(@"3D\A3")
        do police_station <- this.Content.Load(@"3D\station")
        do stars          <- this.Content.Load(@"3D\nightsky")
        do nebula         <- this.Content.Load(@"3D\crab_nebula")
        do laser          <- this.Content.Load(@"3D\dash")

        let enable_lighting (m:Model) =
          for m in m.Meshes do
            for e in m.Effects do
              (e :?> BasicEffect).EnableDefaultLighting()

        do police_ship    |> enable_lighting
        do pirate_ship    |> enable_lighting
        do cargo_ship     |> enable_lighting
        do police_station |> enable_lighting
      
        base.LoadContent()

      override this.Update gt = 
        do PoliceChase.simulation_step(police_chase)

        let dt = gt.ElapsedGameTime.TotalSeconds |> float32
        if Keyboard.GetState().[Keys.A] = KeyState.Down then
          do zoom <- zoom * 1.01f
        elif Keyboard.GetState().[Keys.S] = KeyState.Down then
          do zoom <- zoom / 1.01f

        if Keyboard.GetState().[Keys.Left] = KeyState.Down then
          do center.X <- center.X + dt * 10.0f / (zoom * zoom)
        if Keyboard.GetState().[Keys.Right] = KeyState.Down then
          do center.X <- center.X - dt * 10.0f / (zoom * zoom)
        if Keyboard.GetState().[Keys.Up] = KeyState.Down then
          do center.Y <- center.Y + dt * 10.0f / (zoom * zoom)
        if Keyboard.GetState().[Keys.Down] = KeyState.Down then
          do center.Y <- center.Y - dt * 10.0f / (zoom * zoom)

        do view <- Matrix.CreateLookAt(Vector3.Forward * 30.0f * zoom + center, center, Vector3.Up)
        do basic_effect.World      <- Matrix.Identity
        do basic_effect.View       <- view
        do basic_effect.Projection <- projection

        (*
        We use the MediaPlayer class to play a background music.
        If a song is not playing already (for example because the current song is finished) then we invoke the Play method on the soundtrack, which we load from the Content project:
        *)
        if MediaPlayer.State <> MediaState.Playing then
          do MediaPlayer.Play(this.Content.Load<Song>(@"3D\Soundtrack"))

        base.Update gt

      override this.Draw gt = 
        do this.GraphicsDevice.Clear(Color.Black)

        do sprite_batch.Begin(SpriteSortMode.Immediate, BlendState.Additive)
        do sprite_batch.Draw(
            stars,
            Vector2.Zero, 
            Nullable<_>(),
            Color.White, 
            0.0f, 
            Vector2.Zero, 
            2.0f, 
            SpriteEffects.None,
            0.0f)
        do sprite_batch.Draw(
            nebula,
            Vector2(400.0f,240.0f), 
            Nullable<_>(),
            Color.White, 
            float32 gt.TotalGameTime.TotalMinutes * 2.0f, 
            Vector2(nebula.Width |> float32, nebula.Height |> float32) * 0.5f, 
            0.5f, 
            SpriteEffects.None,
            0.0f)
        do sprite_batch.End()


        let convert_position (position:Vector2) (z:float32) =
          let field_size = float32(PoliceChase.field_size)
          let position = position / field_size
          let position = position * 2.0f - Vector2.One
          Vector3(Vector2(this.GraphicsDevice.Viewport.AspectRatio,1.0f) * 20.0f * position, z)

        sprite_batch.Begin(
          SpriteSortMode.Immediate,
          BlendState.Additive,
          SamplerState.LinearClamp,
          DepthStencilState.None,
          RasterizerState.CullNone,
          basic_effect)
        let draw_laser (position:Vector3) (target:Vector3) (color:Color) =
          let dir = (target - position)
          let dist = dir.Length()
          let rotation = atan2 dir.Y dir.X
          let rotation = rotation
          let laser_size = Vector2(laser.Width |> float32, laser.Height |> float32)
          let t = gt.TotalGameTime.TotalSeconds |> float32 |> ( * ) 15.0f
          do sprite_batch.Draw(
                laser, 
                Vector2(position.X,position.Y), 
                Nullable(), 
                color * (0.7f + 0.3f * sin(t + position.X)), 
                rotation, 
                Vector2(0.0f,laser_size.Y * 0.5f), 
                Vector2(dist,0.2f + cos(position.Y + t) * 0.1f) / laser_size, 
                SpriteEffects.None, 
                0.0f)
        if Chapter2.Math.Vector2.Distance(police_chase.Patrol.Position, police_chase.Pirate.Position) < police_chase.Patrol.WeaponsRange then
          let position = convert_position police_chase.Patrol.Position.ToXNAVector -0.2f
          let target   = convert_position police_chase.Pirate.Position.ToXNAVector -0.2f
          if police_chase.Patrol.Integrity > 0.0<_> && 
             police_chase.Pirate.Integrity > 0.0<_> then
            do draw_laser position target Color.Blue
        if Chapter2.Math.Vector2.Distance(police_chase.Cargo.Position, police_chase.Pirate.Position) < police_chase.Cargo.WeaponsRange then
          let position = convert_position police_chase.Cargo.Position.ToXNAVector 0.0f
          let target   = convert_position police_chase.Pirate.Position.ToXNAVector 0.0f
          if police_chase.Cargo.Integrity > 0.0<_> && 
             police_chase.Pirate.Integrity > 0.0<_> then
            do draw_laser position target Color.YellowGreen
        if Chapter2.Math.Vector2.Distance(police_chase.Patrol.Position, police_chase.Pirate.Position) < police_chase.Pirate.WeaponsRange then
          let position = convert_position police_chase.Pirate.Position.ToXNAVector 0.2f
          let target   = convert_position police_chase.Patrol.Position.ToXNAVector 0.2f
          if police_chase.Patrol.Integrity > 0.0<_> && 
             police_chase.Pirate.Integrity > 0.0<_> then
            do draw_laser position target Color.Red
        elif Chapter2.Math.Vector2.Distance(police_chase.Cargo.Position, police_chase.Pirate.Position) < police_chase.Pirate.WeaponsRange then
          let position = convert_position police_chase.Pirate.Position.ToXNAVector 0.0f
          let target   = convert_position police_chase.Cargo.Position.ToXNAVector 0.0f
          if police_chase.Cargo.Integrity > 0.0<_> && 
             police_chase.Pirate.Integrity > 0.0<_> then
            do draw_laser position target Color.Red
        do sprite_batch.End()


        do this.GraphicsDevice.BlendState        <- BlendState.Opaque
        do this.GraphicsDevice.DepthStencilState <- DepthStencilState.Default

        let draw_model (model:Model) (z:float32) (position:Vector2) (rotation:float32) =
          let position = convert_position position z
          do model.Draw(Matrix.CreateRotationZ(rotation) *
                        Matrix.CreateTranslation(position),
                        view,
                        projection)

        let draw_ship (model:Model) (z:float32) (ship:PoliceChase.Ship) =
          let rotation = (atan2 ship.Velocity.Y ship.Velocity.X) |> float32
          do draw_model model z (ship.Position.ToXNAVector) (rotation-1.5f)

        do draw_model police_station 1.0f (police_chase.PoliceStation.Position.ToXNAVector) (float32 gt.TotalGameTime.TotalMinutes * 2.0f)
        if police_chase.Patrol.Integrity > 0.0<_> then
          do police_chase.Patrol |> draw_ship police_ship 0.0f
        if police_chase.Pirate.Integrity > 0.0<_> then
          do police_chase.Pirate |> draw_ship pirate_ship 0.0f
        if police_chase.Cargo.Integrity > 0.0<_> then
          do police_chase.Cargo  |> draw_ship cargo_ship  0.0f

        (*
        If a ship is destroyed, then we play an explosion sound.
        We do so only once, the first frame a ship is destroyed, thanks to the explosion_played boolean.
        If we remove the check (explosion_played = false) or if we do not set (explosion_played <- true), then the explosion sound will be started once every frame since the first ship is destroyed:
        *)
        if explosion_played = false && 
           (police_chase.Cargo.Integrity < 0.0<_> ||
            police_chase.Pirate.Integrity < 0.0<_> ||
            police_chase.Patrol.Integrity < 0.0<_>) then
          do explosion_played <- true
          do this.Content.Load<SoundEffect>(@"3D\bomb").Play() |> ignore

        base.Draw gt
